package com.growthgate.bootcommon.common.base;

import com.growthgate.bootcommon.annotation.UpdateNullable;
import com.growthgate.bootcommon.util.CollectionUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.data.domain.*;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * BaseService
 *
 * @author GrowthGate
 * @date 2019/12/4
 */
public class BaseServiceImpl<T extends BaseEntity, R extends BaseRepository<T>> implements BaseService<T> {

    protected R repository;

    @Contract(pure = true)
    public BaseServiceImpl(R repository) {
        this.repository = repository;
    }

    @Override
    public T findById(Long id) {
        return id != null ? repository.findById(id).orElse(null) : null;
    }

    @NotNull
    @Override
    public List<T> findByIds(List<Long> ids) {
        return CollectionUtils.isNotEmpty(ids) ? repository.findAllById(ids) : Collections.emptyList();
    }

    @NotNull
    @Override
    public List<T> findByIds(Long... ids) {
        return repository.findAllById(Arrays.asList(ids));
    }

    @NotNull
    @Override
    public List<T> find(@NotNull T entity) {
        return repository.findAll(Example.of(entity, getExampleMatcher()));
    }

    @NotNull
    @Override
    public List<T> find(@NotNull T entity, @NotNull String sortProperty, boolean isAsc) {
        Sort.Order order = isAsc ? Sort.Order.asc(sortProperty) : Sort.Order.desc(sortProperty);
        return repository.findAll(Example.of(entity, getExampleMatcher()), Sort.by(order));
    }

    protected ExampleMatcher getExampleMatcher() {
        return ExampleMatcher.matching();
    }

    @NotNull
    @Override
    public List<T> findAll() {
        return repository.findAll();
    }

    @Override
    public @NotNull List<T> findAll(@NotNull String sortProperty, boolean isAsc) {
        Sort.Order order = isAsc ? Sort.Order.asc(sortProperty) : Sort.Order.desc(sortProperty);
        return repository.findAll(Sort.by(order));
    }

    @Override
    public long count() {
        return repository.count();
    }

    @Override
    public long count(@NotNull T entity) {
        return repository.count(Example.of(entity, getExampleMatcher()));
    }

    @Override
    public @NotNull Page<T> page(int pageNum, int pageSize) {
        PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
        return repository.findAll(pageRequest);
    }

    @Override
    public @NotNull Page<T> page(int pageNum, int pageSize, @NotNull String sortProperty, boolean isAsc) {
        Sort.Order order = isAsc ? Sort.Order.asc(sortProperty) : Sort.Order.desc(sortProperty);
        PageRequest pageRequest = PageRequest.of(pageNum, pageSize, Sort.by(order));
        return repository.findAll(pageRequest);
    }

    @Override
    public @NotNull Page<T> page(@NotNull T entity, int pageNum, int pageSize) {
        PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
        return repository.findAll(Example.of(entity, getExampleMatcher()), pageRequest);
    }

    @Override
    public @NotNull Page<T> page(@NotNull T entity, int pageNum, int pageSize, @NotNull String sortProperty, boolean isAsc) {
        Sort.Order order = isAsc ? Sort.Order.asc(sortProperty) : Sort.Order.desc(sortProperty);
        PageRequest pageRequest = PageRequest.of(pageNum, pageSize, Sort.by(order));
        return repository.findAll(Example.of(entity, getExampleMatcher()), pageRequest);
    }

    @NotNull
    @Override
    public T save(@NotNull T entity) {
        return repository.saveAndFlush(checkUpdateNullProperties(entity));
    }

    @Override
    public @NotNull List<T> saveAll(@NotNull List<T> list) {
        List<T> newList = list.stream().map(this::checkUpdateNullProperties).collect(Collectors.toList());
        List<T> saveList = repository.saveAll(newList);
        repository.flush();
        return saveList;
    }

    private T checkUpdateNullProperties(@NotNull T entity) {
        // 查询数据库是否已存在数据
        T oEntity = findById(entity.getId());
        if (oEntity != null) {
            // 更新数据
            String[] nullProperties = getNullProperties(entity);
            // 使用非空属性覆盖已有数据
            BeanUtils.copyProperties(entity, oEntity, nullProperties);
            return oEntity;
        } else {
            return entity;
        }
    }

    @NotNull
    private String[] getNullProperties(T entity) {
        // 获取bean
        BeanWrapper bean = new BeanWrapperImpl(entity);
        // 获取bean的属性描述
        PropertyDescriptor[] propertyDescriptors = bean.getPropertyDescriptors();
        // 获取bean的空属性
        Set<String> properties = new HashSet<>();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            String propertyName = propertyDescriptor.getName();
            Object propertyValue = bean.getPropertyValue(propertyName);
            if (propertyValue == null) {
                properties.add(propertyName);
            }
        }
        // 获取标注了SaveNullable注解的属性
        List<String> nullableProperties = FieldUtils.getFieldsListWithAnnotation(entity.getClass(), UpdateNullable.class).stream().map(Field::getName)
                .collect(Collectors.toList());
        properties.removeAll(nullableProperties);
        return properties.toArray(new String[]{});
    }

    @Override
    public void deleteById(@NotNull Long id) {
        repository.deleteById(id);
    }

    @Override
    public void deleteByIds(List<Long> ids) {
        List<T> list = findByIds(ids);
        if (!list.isEmpty()) {
            repository.deleteInBatch(list);
        }
    }

    @Override
    public void deleteByIds(Long... ids) {
        deleteByIds(Arrays.asList(ids));
    }

}